package com.stark.wechat.util;

import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import net.sf.json.JSONObject;

/**
 * Java 模拟浏览器发送 HTTP 请求。
 * @author Ben
 * @since 1.0.0
 * @version 1.0.0, 2017/8/5
 */
public class HttpClient {
	/**
	 * 连接超时时间
	 */
	private int connectTimeout;
	/**
	 * 获取Connection超时时间
	 */
	private int connectionRequestTimeout;
	/**
	 * 接收数据超时时间
	 */
	private int socketTimeout;

	/**
	 * 构造方法，默认连接超时时间 3 秒、获取Connection超时时间 1 秒、接收数据超时时间 5 秒。
	 */
	public HttpClient() {
		this.connectTimeout = 3000;
		this.connectionRequestTimeout = 1000;
		this.socketTimeout = 5000;
	}

	/**
	 * 构造方法。
	 * @param connectTimeout 连接超时时间（毫秒）。
	 * @param connectionRequestTimeout 获取Connection超时时间（毫秒）。
	 * @param socketTimeout 接收数据超时时间（毫秒）。
	 */
	public HttpClient(int connectTimeout, int connectionRequestTimeout, int socketTimeout) {
		this.connectTimeout = connectTimeout;
		this.connectionRequestTimeout = connectionRequestTimeout;
		this.socketTimeout = socketTimeout;
	}

	/**
	 * 获取实例化对象，默认连接超时时间 3 秒、获取Connection超时时间 1 秒、接收数据超时时间 5 秒。
	 * @return HTTP 客户端对象。
	 */
	public static HttpClient getInstance() {
		HttpClient httpClient = new HttpClient();
		return httpClient;
	}

	/**
	 * 获取实例化对象。
	 * @param connectTimeout 连接超时时间（毫秒）。
	 * @param connectionRequestTimeout 获取Connection超时时间（毫秒）。
	 * @param socketTimeout 接收数据超时时间（毫秒）。
	 * @return HTTP 客户端对象。
	 */
	public static HttpClient getInstance(int connectTimeout, int connectionRequestTimeout, int socketTimeout) {
		HttpClient httpClient = new HttpClient(connectTimeout, connectionRequestTimeout, socketTimeout);
		return httpClient;
	}

	/**
	 * 发送 get 请求。
	 * @param url 请求 URL 。
	 * @param paramMap 请求参数 map 。
	 * @return 响应字符串。
	 * @throws Exception 发送请求时抛出异常。
	 */
	public String sendGet(String url, Map<String, String> paramMap) throws Exception {
		// 处理请求参数
		String paramStr = "";
		String headers = "";
		if (paramMap != null && paramMap.size() > 0) {
			for (Entry<String, String> entry : paramMap.entrySet()) {
				if ("headers".equals(entry.getKey())) {
					headers = entry.getValue();
					continue;
				}
				paramStr += "&" + entry.getKey() + "=" + entry.getValue();
			}
			if (!"".equals(paramStr)) {
				paramStr = paramStr.substring(1);
			}
		}
		// 处理请求url
		if (url.endsWith("?") || url.endsWith("&")) {
			url += paramStr;
		} else {
			url += (url.contains("?") ? "&" : "?") + paramStr;
		}

		CloseableHttpClient httpClient = null;
		HttpGet httpGet = null;
		String result = null;
		try {
			httpClient = this.getCloseableHttpClient();
			httpGet = new HttpGet(url);
			RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(this.connectTimeout)
					.setConnectionRequestTimeout(this.connectionRequestTimeout).setSocketTimeout(this.socketTimeout)
					.build();
			httpGet.setConfig(requestConfig);
			if (!"".equals(headers)) {
				JSONObject json = JSONObject.fromObject(headers);
				@SuppressWarnings("rawtypes")
				Iterator iter = json.keys();
				String key;
				while (iter.hasNext()) {
					key = (String) iter.next();
					httpGet.setHeader(key, (String) json.get(key));
				}
			}
			HttpResponse response = httpClient.execute(httpGet);
			if (response != null) {
				HttpEntity resEntity = response.getEntity();
				if (resEntity != null) {
					result = EntityUtils.toString(resEntity, "UTF-8");
				}
			}
		} finally {
			if (httpClient != null) {
				try {
					httpClient.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return result;
	}

	/**
	 * 发送 get 请求。
	 * @param url 请求 URL 。
	 * @return 响应字符串。
	 * @throws Exception 发送请求时抛出异常。
	 * @see #sendGet(String, Map)
	 */
	public String sendGet(String url) throws Exception {
		String result = this.sendGet(url, null);
		return result;
	}

	/**
	 * 发送 post 请求。
	 * @param url 请求 URL 。
	 * @param paramMap 请求参数 map 。
	 * @return 响应字符串。
	 * @throws Exception 发送请求时抛出异常。
	 */
	public String sendPost(String url, Map<String, String> paramMap) throws Exception {
		CloseableHttpClient httpClient = null;
		HttpPost httpPost = null;
		String result = null;
		try {
			httpClient = this.getCloseableHttpClient();
			httpPost = new HttpPost(url);
			RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(this.connectTimeout)
					.setConnectionRequestTimeout(this.connectionRequestTimeout).setSocketTimeout(this.socketTimeout)
					.build();
			httpPost.setConfig(requestConfig);
			// 设置参数
			List<NameValuePair> list = new ArrayList<NameValuePair>();
			String headers = "";
			for (Entry<String, String> entry : paramMap.entrySet()) {
				if ("headers".equals(entry.getKey())) {
					headers = entry.getValue();
					continue;
				}
				list.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
			}
			if (list.size() > 0) {
				UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
				httpPost.setEntity(entity);
			}
			if (!"".equals(headers)) {
				JSONObject json = JSONObject.fromObject(headers);
				@SuppressWarnings("rawtypes")
				Iterator iter = json.keys();
				String key;
				while (iter.hasNext()) {
					key = (String) iter.next();
					httpPost.setHeader(key, (String) json.get(key));
				}
			}
			HttpResponse response = httpClient.execute(httpPost);
			if (response != null) {
				HttpEntity resEntity = response.getEntity();
				if (resEntity != null) {
					result = EntityUtils.toString(resEntity, "UTF-8");
				}
			}
		} finally {
			if (httpClient != null) {
				try {
					httpClient.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return result;
	}

	/**
	 * 发送 post 请求。
	 * @param url 请求 URL 。
	 * @param paramStr JSON 字符串。
	 * @return 响应字符串。
	 * @throws Exception 发送请求时抛出异常。
	 */
	public String sendPost(String url, String paramStr) throws Exception {
		CloseableHttpClient httpClient = null;
		HttpPost httpPost = null;
		String result = null;
		try {
			httpClient = this.getCloseableHttpClient();
			httpPost = new HttpPost(url);
			RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(this.connectTimeout)
					.setConnectionRequestTimeout(this.connectionRequestTimeout).setSocketTimeout(this.socketTimeout)
					.build();
			httpPost.setConfig(requestConfig);
			// 设置参数
			if (null != paramStr && paramStr.trim().length() > 0) {
				JSONObject param = JSONObject.fromObject(paramStr);
				if (param.containsKey("headers")) {
					JSONObject headers = param.getJSONObject("headers");
					@SuppressWarnings("rawtypes")
					Iterator iter = headers.keys();
					String key;
					while (iter.hasNext()) {
						key = (String) iter.next();
						httpPost.setHeader(key, headers.getString(key));
					}
					param.remove("headers");
					paramStr = param.toString();
				}
				StringEntity entity = new StringEntity(paramStr, "UTF-8");
				httpPost.setEntity(entity);
			}
			HttpResponse response = httpClient.execute(httpPost);
			if (response != null) {
				HttpEntity resEntity = response.getEntity();
				if (resEntity != null) {
					result = EntityUtils.toString(resEntity, "UTF-8");
				}
			}
		} finally {
			if (httpClient != null) {
				try {
					httpClient.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return result;
	}

	/**
	 * 发送 post 请求。
	 * @param url 请求 URL 。
	 * @return 响应字符串。
	 * @throws Exception 发送请求时抛出异常。
	 * @see #sendPost(String, String)
	 */
	public String sendPost(String url) throws Exception {
		String result = this.sendPost(url, "");
		return result;
	}
	
	/**
	 * 发送 put 请求。
	 * @param url 请求 URL 。
	 * @param paramStr JSON 字符串。
	 * @return 响应字符串。
	 * @throws Exception 发送请求时抛出异常。
	 */
	public String sendPut(String url, String paramStr) throws Exception {
		CloseableHttpClient httpClient = null;
		HttpPut httpPut = null;
		String result = null;
		try {
			httpClient = this.getCloseableHttpClient();
			httpPut = new HttpPut(url);
			RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(this.connectTimeout)
					.setConnectionRequestTimeout(this.connectionRequestTimeout).setSocketTimeout(this.socketTimeout)
					.build();
			httpPut.setConfig(requestConfig);
			// 设置参数
			if (null != paramStr && paramStr.trim().length() > 0) {
				JSONObject param = JSONObject.fromObject(paramStr);
				if (param.containsKey("headers")) {
					JSONObject headers = param.getJSONObject("headers");
					@SuppressWarnings("rawtypes")
					Iterator iter = headers.keys();
					String key;
					while (iter.hasNext()) {
						key = (String) iter.next();
						httpPut.setHeader(key, headers.getString(key));
					}
					param.remove("headers");
					paramStr = param.toString();
				}
				StringEntity entity = new StringEntity(paramStr, "UTF-8");
				httpPut.setEntity(entity);
			}
			HttpResponse response = httpClient.execute(httpPut);
			if (response != null) {
				HttpEntity resEntity = response.getEntity();
				if (resEntity != null) {
					result = EntityUtils.toString(resEntity, "UTF-8");
				}
			}
		} finally {
			if (httpClient != null) {
				try {
					httpClient.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return result;
	}
	
	/**
	 * 发送 delete 请求。
	 * @param url 请求 URL 。
	 * @param paramMap 请求参数 map 。
	 * @return 响应字符串。
	 * @throws Exception 发送请求时抛出异常。
	 */
	public String sendDelete(String url, Map<String, String> paramMap) throws Exception {
		// 处理请求参数
		String paramStr = "";
		String headers = "";
		if (paramMap != null && paramMap.size() > 0) {
			for (Entry<String, String> entry : paramMap.entrySet()) {
				if ("headers".equals(entry.getKey())) {
					headers = entry.getValue();
					continue;
				}
				paramStr += "&" + entry.getKey() + "=" + entry.getValue();
			}
			if (!"".equals(paramStr)) {
				paramStr = paramStr.substring(1);
			}
		}
		// 处理请求url
		if (url.endsWith("?") || url.endsWith("&")) {
			url += paramStr;
		} else {
			url += (url.contains("?") ? "&" : "?") + paramStr;
		}

		CloseableHttpClient httpClient = null;
		HttpDelete httpDelete = null;
		String result = null;
		try {
			httpClient = this.getCloseableHttpClient();
			httpDelete = new HttpDelete(url);
			RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(this.connectTimeout)
					.setConnectionRequestTimeout(this.connectionRequestTimeout).setSocketTimeout(this.socketTimeout)
					.build();
			httpDelete.setConfig(requestConfig);
			if (!"".equals(headers)) {
				JSONObject json = JSONObject.fromObject(headers);
				@SuppressWarnings("rawtypes")
				Iterator iter = json.keys();
				String key;
				while (iter.hasNext()) {
					key = (String) iter.next();
					httpDelete.setHeader(key, (String) json.get(key));
				}
			}
			HttpResponse response = httpClient.execute(httpDelete);
			if (response != null) {
				HttpEntity resEntity = response.getEntity();
				if (resEntity != null) {
					result = EntityUtils.toString(resEntity, "UTF-8");
				}
			}
		} finally {
			if (httpClient != null) {
				try {
					httpClient.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return result;
	}
	
	/**
	 * 自定义 Http 客户端，绕过 https 证书。
	 * @return Http 客户端对象。
	 * @throws NoSuchAlgorithmException 实例化 {@link SSLContext} 对象时抛出异常。
	 * @throws KeyManagementException 自定义 {@link SSLContext} 对象参数时抛出异常。
	 * @see #createIgnoreVerifySSL()
	 */
	public CloseableHttpClient getCloseableHttpClient() throws NoSuchAlgorithmException, KeyManagementException {
		// 采用绕过验证的方式处理https请求
		SSLContext sslcontext = this.createIgnoreVerifySSL();

		// 设置协议http和https对应的处理socket链接工厂的对象
		Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
				.register("http", PlainConnectionSocketFactory.INSTANCE)
				.register("https", new SSLConnectionSocketFactory(sslcontext)).build();
		PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
		HttpClients.custom().setConnectionManager(connManager);

		// 创建自定义的HttpClient对象
		CloseableHttpClient client = HttpClients.custom().setConnectionManager(connManager).build();
		return client;
	}
	
	/**
	 * 创建自定义 SSL 上下文，绕过 https 证书验证。
	 * @return SSL 上下文对象。
	 * @throws NoSuchAlgorithmException 实例化 {@link SSLContext} 对象时抛出异常。
	 * @throws KeyManagementException 自定义 {@link SSLContext} 对象参数时抛出异常。
	 */
	public SSLContext createIgnoreVerifySSL() throws NoSuchAlgorithmException, KeyManagementException {
		SSLContext sc = SSLContext.getInstance("SSLv3");

		// 实现一个X509TrustManager接口，用于绕过验证，不用修改里面的方法
		X509TrustManager trustManager = new X509TrustManager() {
			public void checkClientTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
					String paramString) throws CertificateException {
			}

			public void checkServerTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
					String paramString) throws CertificateException {
			}

			public java.security.cert.X509Certificate[] getAcceptedIssuers() {
				return null;
			}
		};

		sc.init(null, new TrustManager[] { trustManager }, null);
		return sc;
	}
}
